package it.uniroma2.art.coda.core;

import it.uniroma2.art.coda.exception.CODAComponentNotSetException;
import it.uniroma2.art.coda.exception.FelixInitializationException;
import it.uniroma2.art.coda.exception.PRParserException;
import it.uniroma2.art.coda.extension.CODAOSGiManger;
import it.uniroma2.art.coda.interfaces.Converter;
import it.uniroma2.art.coda.interfaces.ParserPR;
import it.uniroma2.art.coda.interfaces.ProjectionRulesDeciderInterface;
import it.uniroma2.art.coda.pearl.model.ProjectionRulesModel;
import it.uniroma2.art.coda.pearl.model.graph.GraphSingleElemBNode;
import it.uniroma2.art.coda.pearl.model.graph.GraphSingleElemLiteral;
import it.uniroma2.art.coda.pearl.model.graph.GraphSingleElemPlaceholder;
import it.uniroma2.art.coda.pearl.model.graph.GraphSingleElemUri;
import it.uniroma2.art.coda.pearl.model.graph.GraphSingleElemVar;
import it.uniroma2.art.coda.pearl.model.graph.GraphSingleElement;
import it.uniroma2.art.coda.pearl.model.BindingStruct;
import it.uniroma2.art.coda.pearl.model.ComponentProxy;
import it.uniroma2.art.coda.pearl.model.GraphElement;
import it.uniroma2.art.coda.pearl.model.GraphStruct;
import it.uniroma2.art.coda.pearl.model.OptionalGraphStruct;
import it.uniroma2.art.coda.pearl.model.PlaceholderStruct;
import it.uniroma2.art.coda.pearl.model.ProjectionRule;
import it.uniroma2.art.coda.provisioning.ComponentProvider;
import it.uniroma2.art.coda.structures.ARTNodeAndString;
import it.uniroma2.art.coda.structures.ARTTriple;
import it.uniroma2.art.coda.structures.CasAnnotationStructure;
import it.uniroma2.art.coda.structures.CodaFinalSuggestions;
import it.uniroma2.art.coda.structures.PreviousDecisionSingleElement;
import it.uniroma2.art.coda.structures.PreviousDecisions;
import it.uniroma2.art.coda.structures.SelectedProjectionRules;
import it.uniroma2.art.coda.structures.SuggOntologyCoda;
import it.uniroma2.art.coda.structures.Tuple;
import it.uniroma2.art.coda.structures.ValueOrUri;
import it.uniroma2.art.coda.structures.WhereResult;
import it.uniroma2.art.coda.structures.depends.DependencyObjOneParam;
import it.uniroma2.art.coda.structures.depends.DependencyObjThreeParam;
import it.uniroma2.art.coda.structures.depends.DependencyObjTwoParam;
import it.uniroma2.art.coda.structures.depends.DependencyObjVarParam;
import it.uniroma2.art.coda.structures.depends.DependencyObject;
import it.uniroma2.art.coda.structures.table.SingleTableValue;
import it.uniroma2.art.coda.structures.table.SingleTableValueVariable;
import it.uniroma2.art.coda.structures.table.TablePlaceholdersBindingsVariables;
import it.uniroma2.art.coda.structures.table.ValueForTable;
import it.uniroma2.art.coda.structures.table.ValueForTableBinding;
import it.uniroma2.art.coda.structures.table.ValueForTablePlaceholder;
import it.uniroma2.art.owlart.exceptions.ModelAccessException;
import it.uniroma2.art.owlart.exceptions.ModelUpdateException;
import it.uniroma2.art.owlart.exceptions.QueryEvaluationException;
import it.uniroma2.art.owlart.exceptions.UnsupportedQueryLanguageException;
import it.uniroma2.art.owlart.io.RDFFormat;
import it.uniroma2.art.owlart.model.ARTBNode;
import it.uniroma2.art.owlart.model.ARTLiteral;
import it.uniroma2.art.owlart.model.ARTNode;
import it.uniroma2.art.owlart.model.ARTNodeFactory;
import it.uniroma2.art.owlart.model.ARTResource;
import it.uniroma2.art.owlart.model.ARTURIResource;
import it.uniroma2.art.owlart.model.NodeFilters;
import it.uniroma2.art.owlart.model.impl.ARTNodeFactoryImpl;
import it.uniroma2.art.owlart.models.OWLArtModelFactory;
import it.uniroma2.art.owlart.models.OWLModel;
import it.uniroma2.art.owlart.models.RDFModel;
import it.uniroma2.art.owlart.navigation.ARTNodeIterator;
import it.uniroma2.art.owlart.query.Binding;
import it.uniroma2.art.owlart.query.MalformedQueryException;
import it.uniroma2.art.owlart.query.Query;
import it.uniroma2.art.owlart.query.QueryLanguage;
import it.uniroma2.art.owlart.query.TupleBindings;
import it.uniroma2.art.owlart.query.TupleBindingsIterator;
import it.uniroma2.art.owlart.query.TupleQuery;
import it.uniroma2.art.owlart.sesame2impl.factory.ARTModelFactorySesame2Impl;
import it.uniroma2.art.owlart.sesame2impl.models.conf.Sesame2ModelConfiguration;
import it.uniroma2.art.owlart.sesame2impl.models.conf.Sesame2NonPersistentInMemoryModelConfiguration;

import java.io.File;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.URL;
import java.text.Bidi;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.felix.framework.Felix;
import org.apache.uima.cas.FSIterator;
import org.apache.uima.jcas.JCas;
import org.apache.uima.jcas.tcas.Annotation;
import org.openrdf.rio.UnsupportedRDFormatException;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.reflections.ReflectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Predicates;

/**
 * This class represents the entry point to the CODA framework and the external
 * tool should only use this directly.
 * 
 * @author <a href="mailto:turbati@info.uniroma2.it">Andrea Turbati</a>
 * @author <a href="mailto:fiorelli@info.uniroma2.it">Manuel Fiorelli</a>
 */
public class CODACore {
	private final String VAR_SYMBOL = "?";
	private final String PLACEHOLDER_SYMBOL = "$";

	private ComponentProvider cp;

	private FSIterator<Annotation> iterAnnotation = null;
	private JCas jcas = null;

	private PreviousDecisions prevDecision;

	private RDFModel rdfModel;

	private HashMap<String, ParserPR> projectionRuleParserMap = null;
	// These maps contains all the possible implementation of the five modules
	// with their ID
	private HashMap<String, ProjectionRulesDeciderInterface> projectionRulesDeciderMap = null;

	// private ParserPR projectionRuleParserSelected = null;
	// These are the selected implementations to be used (one for every module)
	// private ProjectionRulesDeciderInterface projectionRuleDeciderSelected =
	// null;

	// This manage all the extension points
	private CODAOSGiManger codaOSGiManger = null;

	// These int represent the maximum number of solution that each module
	// should return, 0 means no limit
	private int maxNumAnswerPD = 0;

	// This is a model of the ProjectionRule
	ProjectionRulesModel prModel = null;

	// possible type of dependency

	// one parameters
	private String lastDep = "last";
	// private String nextDep = "next";
	// private String encloseDep = "enclose";

	// two parameters
	private String previousDep = "previous";
	// private String previousUpTo = "previousUpTo_";

	// three parameters

	// multiple parameters
	private String lastOneOfDep = "lastOneOf";

	@SuppressWarnings("unused")
	private static Logger logger = LoggerFactory.getLogger(CODACore.class);

	// a list of proxies for external components (like converters inside a PEARL
	// document)
	private List<ComponentProxy> proxies;

	// a temporary directory where the framework will store internal stuff (e.g.
	// the repoDir for the Linked Data resolution)
	private File tempDir;

	/**
	 * Default constructor.
	 */
	public CODACore() {
	}

	/**
	 * Initializes the framework. This method should be called just after the
	 * constructor, before all other methods.
	 * 
	 * @param jcas
	 *            the CAS received form the UIMA AE or AAE
	 * @param oSGiDir
	 *            the directory in which felix will store the cache for its
	 *            bundles
	 * @param rootDir
	 *            the directory in which felix will search for the bundle using
	 *            a particular search strategy
	 * @param layout
	 *            specifies the layout of <code>rootDir</code>
	 * @param projRulePath
	 *            the path of the Projection Rule file
	 * @param rdfModel
	 *            the ontology model created with a particular repository
	 *            manager
	 * @param prevDecision
	 *            is the structure containing the previous decision taken by the
	 *            tool using the suggestions provided by CODA
	 * 
	 * @param tempDir
	 *            a temporary directory used by the framework for storing its
	 *            stuff
	 * @throws Exception
	 */
	public void initialize(File osgiCacheDir, File rootBundlesDir, CODAOSGiManger.Layout layout,
			RDFModel rdfModel, PreviousDecisions prevDecision, File tempDir) throws Exception {
		// Sets the temporary directory
		this.tempDir = tempDir;
		this.codaOSGiManger = CODAOSGiManger.initialize(osgiCacheDir);
		this.codaOSGiManger.loadComponents(rootBundlesDir, layout);

		// remove the bundle loaded in the previous execution of CODA, but not
		// present in this one
		this.codaOSGiManger.removeOldBundle();
		setRDFModel(rdfModel);
		this.prevDecision = prevDecision;
		this.tempDir = tempDir;
		projectionRuleParserMap = codaOSGiManger.getProjectionRuleParserMap();

		File discovererTempDir = new File(tempDir, "discoverer");
		discovererTempDir.mkdirs();
		
		this.cp = new ComponentProvider(codaOSGiManger.getFramework().getBundleContext(), discovererTempDir);
	}

	public ProjectionRulesModel setProjectionRulesModel(File prFile) throws PRParserException {
		ParserPR parser = projectionRuleParserMap.values().iterator().next();
		parser.initialize(this);
		this.prModel = parser.parsePearlDocument(prFile);
		return prModel;
	}

	/**
	 * Set the UIMA CAS containing all the UIMA annotation
	 * 
	 * @param jcas
	 *            the jcas that will be set
	 */
	public void setJCas(JCas jcas) {
		this.jcas = jcas;
		if (jcas != null) {
			iterAnnotation = this.jcas.getAnnotationIndex().iterator();
		}
	}

	/**
	 * Close the rdfModel and stop Felix
	 * 
	 * @throws ModelUpdateException
	 */
	public void stopAndClose() throws ModelUpdateException {
		rdfModel.close();
		cp.dispose();
		codaOSGiManger.stop();
	}

	// /**
	// * Set the Projection Rules and parse it
	// * @param projectionRulesFile the file which contains the Projection Rules
	// to be loaded
	// * @throws PRParserException
	// */
	// public void setProjectionRules(File projectionRulesFile) throws
	// PRParserException{
	// if(projectionRulesFile != null){
	// // Reset the list of proxies
	// this.proxies = new ArrayList<ComponentProxy>();
	// projectionRuleParserSelected.initialize(prModel,
	// projectionRulesFile.getAbsolutePath(), this);
	// projectionRuleParserSelected.parsePR();
	// // Bind proxies to concrete components
	// bindProxies();
	// }
	// }

	/**
	 * Set the ontology model (RDFModel)
	 * 
	 * @param rdfModel
	 *            the ontology model
	 */
	public void setRDFModel(RDFModel rdfModel) {
		this.rdfModel = rdfModel;
		// setProjectionRulesDecider(projectionRuleDeciderSelected);
	}

	/**
	 * Close the RDFModel
	 * 
	 * @throws ModelUpdateException
	 */
	public void closeRDFModel() throws ModelUpdateException {
		rdfModel.close();
	}

	/**
	 * Get the Projection Rule Model
	 * 
	 * @return the projection rule model
	 */
	public ProjectionRulesModel getProjRuleModel() {
		return prModel;
	}

	/**
	 * This method get the Projection Rule Decider Annotation Map TODO check if
	 * it should still remain in this version
	 * 
	 * @return the Projection Disambiguation Map
	 */
	public HashMap<String, ProjectionRulesDeciderInterface> getProjectionRulesDeciderMap() {
		return projectionRulesDeciderMap;
	}

	/**
	 * This method return true if there is another UIMA annotation in the cas,
	 * false otherwise
	 * 
	 * @return true if there is another UIMA annotation in the cas, false
	 *         otherwise
	 */
	public boolean isAnotherAnnotationPresent() {
		if (iterAnnotation != null)
			return iterAnnotation.hasNext();
		else
			// the iterator was not set
			return false;
	}

	/**
	 * This method set the maximum number of hint that the Projection
	 * Disambiguation can return
	 * 
	 * @param maxNumAnswerPD
	 *            the maximum number of hint that the Projection Disambiguation
	 *            can return
	 */
	// OLD version
	public void setMaxNumAnswerPD(int maxNumAnswerPD) {
		this.maxNumAnswerPD = maxNumAnswerPD;
	}

	public SuggOntologyCoda processNextAnnotation() throws Exception {
		if (isAnotherAnnotationPresent() == false)
			return null;

		Annotation annotation = iterAnnotation.next();
		String annotationTypeName = annotation.getType().getName();

		// TODO: type inheritance is not supported (maybe delegate the task to
		// the projection rule decider)
		Collection<ProjectionRule> candidatedRules = prModel
				.getNonLazyProjectionRulesByTypeName(annotationTypeName);

		SuggOntologyCoda suggOntCoda = new SuggOntologyCoda(annotation);
		for (ProjectionRule pr : candidatedRules) {
			suggOntCoda.addProjectionRule(pr);
			executeProjectionRule(pr, annotation, suggOntCoda);
		}
		return suggOntCoda;
	}

	private void executeProjectionRule(ProjectionRule pr, Annotation annotation, SuggOntologyCoda suggOntCoda)
			throws Exception {
		List<ARTTriple> suggestedTripleWithDuplicated = new ArrayList<ARTTriple>();
		List<ARTTriple> suggestedTriple = new ArrayList<ARTTriple>();

		List<List<ValueForTable>> valueForTableListList = new ArrayList<List<ValueForTable>>();

		// Evaluate bindings
		Map<String, BindingStruct> bindingMap = pr.getBindingsMap();
		for (String bindingId : bindingMap.keySet()) {
			BindingStruct binding = bindingMap.get(bindingId);
			String uimaTypeAndFeat = pr.getUIMAType() + ":" + binding.getFeaturePath();
			String bindedRuleId = binding.getBoundRuleId();
			ProjectionRule lazyRule = prModel.getProjRuleFromId(bindedRuleId);
			// now get the placeholder of the bounded lazy rule and construct
			// the ValueForTableBinding
			Map<String, PlaceholderStruct> placeholderFromLazyMap = lazyRule.getPlaceholderMap();
			Map<String, List<String>> valuesFromLazyRuleListMap = new HashMap<String, List<String>>();
			for (String placeholderKey : placeholderFromLazyMap.keySet()) {
				PlaceholderStruct placeholderFromLazyRule = placeholderFromLazyMap.get(placeholderKey);
				String uimaTypeAndFeatInLazyRule = uimaTypeAndFeat + "/"
						+ placeholderFromLazyRule.getFeaturePath();
				List<String> valuesList = UIMACODAUtilities.getValuesOfFeatureFromFeatPath(annotation,
						uimaTypeAndFeatInLazyRule);
				valuesFromLazyRuleListMap.put(placeholderFromLazyRule.getName(), valuesList);
			}
			// now get the value extracted from UIMA using the feature path from
			// the binding and associated
			// then to construct the list of ValueForTableBinding
			boolean errorInBindicgConstruct = false;
			if (!valuesFromLazyRuleListMap.isEmpty()) {
				int numberOfValues = -1;
				for (String key : valuesFromLazyRuleListMap.keySet()) {
					if (numberOfValues == -1)
						numberOfValues = valuesFromLazyRuleListMap.get(key).size();
					if (valuesFromLazyRuleListMap.get(key).size() != numberOfValues)
						errorInBindicgConstruct = true;
				}
				if (errorInBindicgConstruct) {
					// there is some problem in constructing the binding values,
					// the different list of values
					// returned from the featurePath are not even among the
					// different placeholder, so
					// it is not possible in this implementation to do the right
					// association
					// TODO do something
				} else {
					List<ValueForTable> valueForTableBindingList = new ArrayList<ValueForTable>();
					for (int i = 0; i < numberOfValues; ++i) {
						Map<String, ARTNode> idToARTNodeMap = new HashMap<String, ARTNode>();
						for (String key : valuesFromLazyRuleListMap.keySet()) {
							PlaceholderStruct placeholder = placeholderFromLazyMap.get(key);
							String valueFromUIMA = valuesFromLazyRuleListMap.get(key).get(i);

							ARTNode artNode = getARTNodeFromUIMAValue(placeholder, valueFromUIMA);

							// TODO check if the it used in the following map is
							// correct, it should be
							// something like $binding.placeholder
							idToARTNodeMap.put(PLACEHOLDER_SYMBOL + bindedRuleId + "." + key, artNode);
						}
						ValueForTableBinding valueForBinding = new ValueForTableBinding(binding,
								idToARTNodeMap);
						valueForTableBindingList.add(valueForBinding);
					}
					valueForTableListList.add(valueForTableBindingList);
				}
			}
		}

		// Evaluate placeholders
		Map<String, PlaceholderStruct> palceholderMap = pr.getPlaceholderMap();
		for (String placeholderId : palceholderMap.keySet()) {
			PlaceholderStruct placeholder = palceholderMap.get(placeholderId);
			String uimaTypeAndFeat = pr.getUIMAType() + ":" + placeholder.getFeaturePath();
			List<String> valuesList = UIMACODAUtilities.getValuesOfFeatureFromFeatPath(annotation,
					uimaTypeAndFeat);
			
			List<String> convertersList = placeholder.getConverterList();
			List<ValueForTable> valueForTableList = new ArrayList<ValueForTable>();
			for (String valueFromUIMA : valuesList) {

				ARTNode artNode = getARTNodeFromUIMAValue(placeholder, valueFromUIMA);

				// TODO check if before the id it should be put the $ or not
				ValueForTablePlaceholder valueForTablePlaceholder = new ValueForTablePlaceholder(placeholder,
						PLACEHOLDER_SYMBOL + placeholderId, artNode);
				valueForTableList.add(valueForTablePlaceholder);
			}

			if (!valueForTableList.isEmpty())
				valueForTableListList.add(valueForTableList);
		}

		// Construct placeholders/bindings table
		TablePlaceholdersBindingsVariables table = new TablePlaceholdersBindingsVariables();
		table.constructPlaceholderBindingMap(valueForTableListList);

		// For each row in the table, execute where
		Iterator<Map<String, SingleTableValue>> iterTempTable = table.getIteratorForPlaceholderBindingMap();
		while (iterTempTable.hasNext()) {
			Map<String, SingleTableValue> row = iterTempTable.next();
			// If there is no WHERE section (or if it is empty) construct the
			// second table from the first one
			if (pr.getWhereList().isEmpty()) {
				table.addVariableValueToPlaceholderBindingVariableMap(row.values(), null);
			} else {

				Collection<GraphElement> whereList = pr.getWhereList();
				String tempString = constructWhereQuery(whereList, row);

				// For each tuple in the where result set, create a new extended
				// row
				Map<String, List<ARTNode>> varToArtNodeMap = new HashMap<String, List<ARTNode>>();
				if (tempString != null) {
					String query = "SELECT *" + "\nWHERE { ";

					query += tempString;

					query += "\n";
					TupleQuery tupleQuery = rdfModel.createTupleQuery(QueryLanguage.SPARQL, query);
					TupleBindingsIterator tupleIter = tupleQuery.evaluate(true);
					while (tupleIter.hasNext()) {
						TupleBindings tuple = tupleIter.next();
						Set<String> names = tuple.getBindingNames();
						Iterator<String> iterNames = names.iterator();
						while (iterNames.hasNext()) {
							String name = iterNames.next();
							ARTNode artNode = tuple.getBinding(name).getBoundValue();
							String var = VAR_SYMBOL + name;
							if (!varToArtNodeMap.containsKey(var))
								varToArtNodeMap.put(var, new ArrayList<ARTNode>());
							varToArtNodeMap.get(var).add(artNode);
						}
					}
					tupleIter.close();
				} else {
					// there was a problem in the creation of the SPARQL query,
					// so no query is executed
				}

				// use the result of the query to construct the second and final
				// table
				List<List<SingleTableValueVariable>> valueVariableList = new ArrayList<List<SingleTableValueVariable>>();
				for (String var : varToArtNodeMap.keySet()) {
					List<SingleTableValueVariable> varSingleTableValueList = new ArrayList<SingleTableValueVariable>();
					for (ARTNode artNode : varToArtNodeMap.get(var)) {
						varSingleTableValueList.add(new SingleTableValueVariable(var, artNode));
					}
					valueVariableList.add(varSingleTableValueList);
				}
				table.addVariableValueToPlaceholderBindingVariableMap(row.values(), valueVariableList);
			}
		}

		// Use the table construct after the SPARQL queries to create the
		// suggestions
		// iterate over the final table and for every row create the RDF graph
		Iterator<Map<String, SingleTableValue>> iterFinalTable = table
				.getIteratorForPlaceholderBindingVariableMap();
		Collection<GraphElement> graphList = pr.getGraphList();
		while (iterFinalTable.hasNext()) {
			boolean graphFail = false;
			List<ARTTriple> currentSuggestedTriple = new ArrayList<ARTTriple>();
			Map<String, SingleTableValue> row = iterFinalTable.next();
			Map<String, ARTBNode> bnodeMap = new HashMap<String, ARTBNode>();
			for (GraphElement graphElement : graphList) {
				if (graphElement instanceof GraphStruct) {
					GraphStruct graphStruct = (GraphStruct) graphElement;
					ARTTriple suggestedARTTriple = constructARTTriple(graphStruct, row, bnodeMap);
					if (suggestedARTTriple == null) {
						graphFail = true;
						break;
					}
					currentSuggestedTriple.add(suggestedARTTriple);
				} else { // graphElement instanceof OptionalGraphStruct
					OptionalGraphStruct optionaGraphStruct = (OptionalGraphStruct) graphElement;
					List<ARTTriple> optionalSuggestedTripleList = new ArrayList<ARTTriple>();

					for (GraphStruct graphStruct : optionaGraphStruct.getOptionalTriples()) {
						ARTTriple suggestedARTTriple = constructARTTriple(graphStruct, row, bnodeMap);
						if (suggestedARTTriple != null)
							optionalSuggestedTripleList.add(suggestedARTTriple);
					}
					if (!optionalSuggestedTripleList.isEmpty())
						currentSuggestedTriple.addAll(optionalSuggestedTripleList);
				}
			}

			if (!graphFail)
				suggestedTripleWithDuplicated.addAll(currentSuggestedTriple);
		}

		// Remove the duplicate RDF triple from the suggested ones
		removeDuplicate(suggestedTripleWithDuplicated, suggestedTriple);

		// Add the suggestedTriple to the output structure
		suggOntCoda.addSuggestedTripleList(suggestedTriple);
	}

	private ARTNode getARTNodeFromUIMAValue(PlaceholderStruct placeholder, String valueFromUIMA)
			throws Exception {
		Object value = valueFromUIMA;
		for (String converter : placeholder.getConverterList()) {
			Object converterObj = cp.lookup(converter);

			if (placeholder.getRDFType().equalsIgnoreCase("literal")) {
				Set<Method> methods = ReflectionUtils.getMethods(
						converterObj.getClass(),
						Predicates.and(ReflectionUtils.withName("produceLiteral"),
								ReflectionUtils.withParametersAssignableTo(String.class, String.class, value.getClass())));
				Method m = methods.iterator().next();
				value = m.invoke(converterObj, placeholder.getLiteralDatatype(), placeholder.getLiteralLang(), value);
				System.err.println(value + " " + placeholder.getLiteralDatatype() + " " + placeholder.getLiteralLang());
			} else {
				Set<Method> methods = ReflectionUtils.getMethods(
						converterObj.getClass(),
						Predicates.and(ReflectionUtils.withName("produceURI"),
								ReflectionUtils.withParametersAssignableTo(String.class, value.getClass())));
				Method m = methods.iterator().next();
				value = m.invoke(converterObj, rdfModel.getDefaultNamespace(), value);
			}
		}

		return (ARTNode) value;
	}

	private ARTTriple constructARTTriple(GraphStruct graphStruct, Map<String, SingleTableValue> row,
			Map<String, ARTBNode> bnodeMap) {
		GraphSingleElement subjGSE = graphStruct.getSubject();
		GraphSingleElement predGSE = graphStruct.getPredicate();
		GraphSingleElement objGSE = graphStruct.getObject();
				
		ARTNode tempARTNode;
		ARTResource subjARTNode = null;
		tempARTNode = getValueOfGraph(row, subjGSE, bnodeMap);
		if (tempARTNode != null)
			subjARTNode = tempARTNode.asResource();
		ARTURIResource predARTNode = null;
		tempARTNode = getValueOfGraph(row, predGSE, bnodeMap);
		if (tempARTNode != null)
			predARTNode = tempARTNode.asURIResource();
		ARTNode objARTNode = getValueOfGraph(row, objGSE, bnodeMap);
		if (subjARTNode == null || predARTNode == null || objARTNode == null) {
			return null;
		}
		ARTTriple suggestedARTTriple = new ARTTriple(subjARTNode, predARTNode, objARTNode, 1.0);
		return suggestedARTTriple;
	}

	private ARTNode getValueOfGraph(Map<String, SingleTableValue> row, GraphSingleElement graphSingleElement,
			Map<String, ARTBNode> bnodeMap) {
		ARTNode artNode = null;
		
		if (graphSingleElement instanceof GraphSingleElemPlaceholder) {
			GraphSingleElemPlaceholder gsePlaceholder = (GraphSingleElemPlaceholder) graphSingleElement;
						
			if (row.containsKey(gsePlaceholder.getName())) {
				artNode = row.get(gsePlaceholder.getName()).getArtNode();
			}
		} else if (graphSingleElement instanceof GraphSingleElemUri) {
			GraphSingleElemUri gseUri = (GraphSingleElemUri) graphSingleElement;
			artNode = rdfModel.createURIResource(gseUri.getURI());
		} else if (graphSingleElement instanceof GraphSingleElemVar) {
			GraphSingleElemVar gseVar = (GraphSingleElemVar) graphSingleElement;
			if (row.containsKey(gseVar.getVarId())) {
				artNode = row.get(gseVar.getVarId()).getArtNode();
			} else if (row.containsKey(gseVar.getVarId().replace(VAR_SYMBOL, PLACEHOLDER_SYMBOL))) {
				artNode = row.get(gseVar.getVarId().replace(VAR_SYMBOL, PLACEHOLDER_SYMBOL)).getArtNode();
			}
		} else if (graphSingleElement instanceof GraphSingleElemBNode) {
			GraphSingleElemBNode gseBNode = (GraphSingleElemBNode) graphSingleElement;
			if (!bnodeMap.containsKey(gseBNode.getBnodeIdentifier())) {
				ARTBNode artBNode = rdfModel.createBNode();
				bnodeMap.put(gseBNode.getBnodeIdentifier(), artBNode);
			}
			artNode = bnodeMap.get(gseBNode.getBnodeIdentifier());
		} else if (graphSingleElement instanceof GraphSingleElemLiteral) {
			GraphSingleElemLiteral gseLiteral = (GraphSingleElemLiteral) graphSingleElement;
			// TODO deal with the language and datatype
			artNode = rdfModel.createLiteral(gseLiteral.getStringValue());
		}
		return artNode;
	}

	private String constructWhereQuery(Collection<GraphElement> whereList, Map<String, SingleTableValue> row) {
		String whereBody = null;
		String tempString = null;
		HashMap<String, ARTBNode> bnodeMap = new HashMap<String, ARTBNode>();
		for (GraphElement graphElement : whereList) {
			if (graphElement instanceof GraphStruct) {
				GraphStruct graphStruct = (GraphStruct) graphElement;
				tempString = constructWhereQueryTriplePart(graphStruct, row, bnodeMap);
				if (tempString == null)
					return null;
				whereBody += tempString;
			} else { // graphElement instanceof OptionalGraphStruct
				OptionalGraphStruct optionaGraphStruct = (OptionalGraphStruct) graphElement;
				whereBody = "\nOPTIONAL { ";
				for (GraphStruct graphStruct : optionaGraphStruct.getOptionalTriples()) {
					tempString = constructWhereQueryTriplePart(graphStruct, row, bnodeMap);
					if (tempString == null)
						return null;
					whereBody += tempString;
				}

				whereBody += "\n} ";
			}
		}

		return whereBody;
	}

	private String constructWhereQueryTriplePart(GraphStruct graphStruct, Map<String, SingleTableValue> row,
			HashMap<String, ARTBNode> bnodeMap) {
		String query = "\n";
		String tempString;

		tempString = constructWhereQuerySingleElemOfTriplePart(graphStruct.getSubject(), row, bnodeMap);
		if (tempString == null)
			return null;
		query += tempString + " ";

		tempString = constructWhereQuerySingleElemOfTriplePart(graphStruct.getPredicate(), row, bnodeMap);
		if (tempString == null)
			return null;
		query += tempString + " ";

		tempString = constructWhereQuerySingleElemOfTriplePart(graphStruct.getObject(), row, bnodeMap);
		if (tempString == null)
			return null;
		query += tempString + " .";

		return query;
	}

	private String constructWhereQuerySingleElemOfTriplePart(GraphSingleElement graphSingleElement,
			Map<String, SingleTableValue> row, HashMap<String, ARTBNode> bnodeMap) {
		String query = null;

		if (graphSingleElement instanceof GraphSingleElemPlaceholder) {
			GraphSingleElemPlaceholder gsePlaceholder = (GraphSingleElemPlaceholder) graphSingleElement;
			if (row.containsKey(gsePlaceholder.getName())) {
				ARTNode artNode = row.get(gsePlaceholder.getName()).getArtNode();
				if (artNode == null)
					return null;
				query = getValueForWhere(artNode);

			}
		} else if (graphSingleElement instanceof GraphSingleElemUri) {
			GraphSingleElemUri gseUri = (GraphSingleElemUri) graphSingleElement;
			ARTNode artNode = rdfModel.createURIResource(gseUri.getURI());
			if (artNode == null)
				return null;
			query = getValueForWhere(artNode);
		} else if (graphSingleElement instanceof GraphSingleElemVar) {
			GraphSingleElemVar gseVar = (GraphSingleElemVar) graphSingleElement;
			query = gseVar.getVarId();
		} else if (graphSingleElement instanceof GraphSingleElemBNode) {
			GraphSingleElemBNode gseBNode = (GraphSingleElemBNode) graphSingleElement;
			if (!bnodeMap.containsKey(gseBNode.getBnodeIdentifier())) {
				ARTBNode artBNode = rdfModel.createBNode();
				bnodeMap.put(gseBNode.getBnodeIdentifier(), artBNode);
			}
			ARTNode artNode = bnodeMap.get(gseBNode.getBnodeIdentifier());
			query = getValueForWhere(artNode);
		} else if (graphSingleElement instanceof GraphSingleElemLiteral) {
			GraphSingleElemLiteral gseLiteral = (GraphSingleElemLiteral) graphSingleElement;
			// TODO deal with the language and datatype
			ARTNode artNode = rdfModel.createLiteral(gseLiteral.getStringValue());
			if (artNode == null)
				return null;
			query = getValueForWhere(artNode);
		}
		return query;
	}

	private String getValueForWhere(ARTNode artNode) {
		String value = null;
		if (artNode.isURIResource())
			value = "<" + artNode.getNominalValue() + ">";
		else if (artNode.isLiteral()) {
			ARTLiteral artLiteral = artNode.asLiteral();
			value = "\"" + artLiteral.getLabel() + "\"";
			if (artLiteral.getLanguage() != null)
				value += "@" + artLiteral.getLanguage();
			else if (artLiteral.getDatatype() != null)
				value += "^^" + artLiteral.getDatatype().getURI();
		} else if (artNode.isBlank())
			value = "_:" + artNode.getNominalValue();
		return value;
	}

	private void removeDuplicate(List<ARTTriple> suggestedTripleWithDuplicated,
			List<ARTTriple> suggestedTriple) {
		List<String> uniqueTriple = new ArrayList<String>();
		for (ARTTriple artTriple : suggestedTripleWithDuplicated) {
			StringBuilder stringBuilder = new StringBuilder();
			ARTResource subj = artTriple.getSubject();
			ARTURIResource pred = artTriple.getPredicate();
			ARTNode obj = artTriple.getObject();
			stringBuilder.append(subj.getNominalValue());
			stringBuilder.append("|_|");
			stringBuilder.append(pred.getNominalValue());
			stringBuilder.append("|_|");
			if (obj.isLiteral()) {
				ARTLiteral objLiteral = obj.asLiteral();
				stringBuilder.append(objLiteral.getLabel());
				if (objLiteral.getLanguage() != null)
					stringBuilder.append("@" + objLiteral.getLanguage());
				if (objLiteral.getDatatype() != null)
					stringBuilder.append("^^" + objLiteral.getDatatype().getNominalValue());
			} else { // obj is a resource
				stringBuilder.append(obj.getNominalValue());
			}

			if (!uniqueTriple.contains(stringBuilder.toString())) {
				uniqueTriple.add(stringBuilder.toString());
				suggestedTriple.add(artTriple);
			}
		}
	}
}
